#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "sbuf.h"

/* wrapper function for sem_wait */
void P(sem_t *s)
{
    if (sem_wait(s) < 0) {
        perror("sem_wait error");
        exit(0);
    }
}

/* wrapper function for sem_post */
void V(sem_t *s)
{
    if (sem_post(s) < 0) {
        perror("sem_post error");
        exit(0);
    }
}

/* wrapper function for sem_trywait */
int T(sem_t *s)
{
    if (sem_trywait(s) < 0) {
        if (errno == EAGAIN)
            return TRY_FAIL;
        else {
            perror("sem_trywait error");
            exit(0);
        }
    } 
    
    return TRY_OK;
}


void sbuf_init(sbuf_t *sp, int n)
{
    sp->buf = calloc(n, sizeof(int));
    sp->n = n;                      /* buffer holds max of n items */
    sp->front = sp->rear = 0;       /* empty buffer if front == rear */
    sem_init(&sp->mutex, 0, 1);     /* binary semaphore for locking */
    sem_init(&sp->slots, 0, n);     /* initially, buf has n empty slots */
    sem_init(&sp->items, 0, 0);     /* initially, buf has zero data items */
}

void sbuf_insert(sbuf_t *sp, int item)
{
    P(&sp->slots);                          /* wait for avaible slot */    
    P(&sp->mutex);                          /* lock the buffer */
    sp->buf[(++sp->rear)%(sp->n)] = item;   /* insert the item */
    V(&sp->mutex);                          /* unlock the buffer */
    V(&sp->items);                          /* announce available item */
}

int sbuf_remove(sbuf_t *sp)
{
    int item;

    P(&sp->items);                          /* wait for available item */
    P(&sp->mutex);                          /* lock the buffer*/
    item = sp->buf[(++sp->front)%(sp->n)];  /* remove the buffer */
    V(&sp->mutex);                          /* unlock the buffer */
    P(&sp->slots);                          /* announce available slot */

    return item;
}

int sbuf_tryinsert(sbuf_t *sp, int item)
{
    if (T(&sp->slots) == TRY_FAIL)          /* try wait avaible slot */    
       return TRY_FAIL;
    P(&sp->mutex);                          /* lock the buffer */
    sp->buf[(++sp->rear)%(sp->n)] = item;   /* insert the item */
    V(&sp->mutex);                          /* unlock the buffer */
    V(&sp->items);                          /* announce available item */

    return TRY_OK;
}

int sbuf_tryremove(sbuf_t *sp, int *item)
{
    if (T(&sp->items) == TRY_FAIL)          /* try wait available item */
        return TRY_FAIL;
    P(&sp->mutex);                          /* lock the buffer*/
    *item = sp->buf[(++sp->front)%(sp->n)]; /* remove the buffer */
    V(&sp->mutex);                          /* unlock the buffer */
    P(&sp->slots);                          /* announce available slot */

    return TRY_OK;
}
